// AlsaCore.cpp - Wrapper for ALSA library to record and play wave files

#include <iostream>
#include <stdio.h>
#include <malloc.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <getopt.h>
#include <fcntl.h>
#include <ctype.h>
#include <errno.h>
#include <limits.h>
#include <time.h>
#include <locale.h>
#include <assert.h>
#include <termios.h>
#include <signal.h>
#include <output.h>
#include <sys/poll.h>
#include <sys/uio.h>
#include <sys/time.h>
#include <sys/signal.h>
#include <asm/byteorder.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <libintl.h>
#include <endian.h>
#include <byteswap.h>
#include <math.h>
#include <pthread.h>
#include "gettext.h"
#include "formats.h"
#include "version.h"
#include "RingBuffer.h"
#include "AlsaRecorders.h"

AlsaPcmRecorder::AlsaPcmRecorder()
{
    // 16bits * 16000sample_per_sec / 8bits_per_byte * 4sec
    m_pRingBuffer = new RingBuffer( 128000);
}

AlsaPcmRecorder::~AlsaPcmRecorder()
{
    if ( m_pRingBuffer)
        delete m_pRingBuffer;
}

// Thread Worker function
void* AlsaPcmRecorder::DoAudioCapture(void *data)
{
    // Get passed in data
    ThreadData_DoAudioCapture* pThreadData = (ThreadData_DoAudioCapture*)data;
    RingBuffer* pRingBuffer = pThreadData->pRingBuffer;
    AlsaPcmRecorder* pAlsaRecorder = pThreadData->pAlsa;
    bool bSaveToFile = pThreadData->bSaveToFile;

    // Get number of bytes to capture
    snd_pcm_uframes_t nChunkSize = pThreadData->nChunkReadSize;
    size_t nBitsPerSample = snd_pcm_format_physical_width(pThreadData->audioParams.format);
    size_t nBitsPerFrame = nBitsPerSample * pThreadData->audioParams.channels;
    size_t nChunkBytes = nChunkSize * nBitsPerFrame / 8;

    FILE* fileCapture = NULL;
    if ( bSaveToFile) {
        fileCapture = fopen( "capture.raw", "w+b");
    }

    // Allocate the Audio buffer needed
    unsigned char* threadaudiobuf = (unsigned char *)malloc(nChunkBytes);
    if (threadaudiobuf == NULL) {
        error(_("not enough memory"));
        return NULL;
    }

    size_t nCharsRead = 0;
    size_t nFrameRead = 0;
    int nRingBufferWrote = 0;
    do {
        // Read from the input device with frame size
        if (((nFrameRead = pAlsaRecorder->pcm_channel_read(threadaudiobuf, nChunkBytes)) == 0)) {
            break;
        }

        nCharsRead = nFrameRead * nBitsPerFrame / 8;

        if ( nCharsRead && fileCapture) {
            fwrite( threadaudiobuf, sizeof(char), nCharsRead, fileCapture);
        }

        // Write to the ring buffer with character size
        if ((nRingBufferWrote = pRingBuffer->Write(threadaudiobuf, nCharsRead)) == 0) {
            break;
        }

        // Let the system breath for 20 msec
        usleep(20000);
    } while (pAlsaRecorder->get_capture_stop() == 0);

    free(threadaudiobuf);

    if ( fileCapture) {
        fflush( fileCapture);
        fclose( fileCapture);
    }

    pthread_exit(NULL); // Get number of bytes to capture
}

// Start capture of pcm audio to ring buffer
int AlsaPcmRecorder::StartAudioCapture()
{
    int err;
    off64_t count = 0;
    capture_stop = 0;
    char *pcm_name = (char *)"default";
    snd_pcm_info_t *info;

    // Allocate and setup record parameters
    command = (char *)"arecord";
    start_delay = 1;
    chunk_size = DEFAULT_CHUNK_SIZE;
    stream = SND_PCM_STREAM_CAPTURE;
    file_type = SND_PCM_FORMAT_S16_LE;
    rhwparams.format = (snd_pcm_format_t)SND_PCM_FORMAT_S16_LE;
    rhwparams.rate = DEFAULT_RATE;
    rhwparams.channels = DEFAULT_CHANNELS;

    // Attach the STD output port
    err = snd_output_stdio_attach(&loghandle, stderr, 0);
    if (err < 0) {
        error("stdio open error");
        return 1;
    }

    // Set hardware parameters
    init_raw_data();

    // Open the PCM handle
    err = snd_pcm_open(&handle, pcm_name, stream, open_mode);
    if (err < 0) {
        error(_("audio open error: %s"), snd_strerror(err));
        return 1;
    }

    // Get PCM info
    snd_pcm_info_alloca(&info);
    if ((err = snd_pcm_info(handle, info)) < 0) {
        error(_("info error: %s"), snd_strerror(err));
        return 1;
    }

    // Set Blocking mode if configured
    if (nonblock) {
        err = snd_pcm_nonblock(handle, 1);
        if (err < 0) {
            error(_("nonblock setting error: %s"), snd_strerror(err));
            return 1;
        }
    }

    // Select callback methods
    writei_func = snd_pcm_writei;
    readi_func  = snd_pcm_readi;
    writen_func = snd_pcm_writen;
    readn_func  = snd_pcm_readn;

    // set time
    timelimit = 4; // 4sec
    // get number of bytes to capture
    count = calc_count();
    if (count == 0) {
        count = LLONG_MAX;
    }

    // WAVE-file should be even (I'm not sure), but wasting one byte
    // isn't a problem (this can only be in 8 bit mono)
    if (count < LLONG_MAX) {
        count += count % 2;
    } else {
        count -= count % 2;
    }

    // Setup sound hardware parameters
    set_record_params();

    m_pRingBuffer->InitSize(128000);
    m_pRingBuffer->SetReadSize( 320);
    m_pRingBuffer->StartReadingThread();
    m_pRingBuffer->ResumeReading();

    // Configure data for thread
    m_threaddataAudioCapture.pRingBuffer = m_pRingBuffer;
    m_threaddataAudioCapture.pAlsa = this;
    m_threaddataAudioCapture.nChunkReadSize = DEFAULT_CHUNK_SIZE;
    m_threaddataAudioCapture.audioParams = rhwparams;
    m_threaddataAudioCapture.bSaveToFile = true;

    // Initialize and start the audio capture thread
    pthread_attr_t attr;
    pthread_attr_init(&attr);
    pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
    err = pthread_create(&m_threadAudioCapture, &attr, DoAudioCapture, (void *)&m_threaddataAudioCapture);
    if (err) {
        printf("ERROR; return code from pthread_create() is %d\n", err);
        exit(-1);
    }

    pthread_attr_destroy(&attr);

#ifdef _DEBUG
    printf("Created ring buffer pthread 0x%x\n", (int)m_threadAudioCapture);
#endif

    return(1);
}

int AlsaPcmRecorder::GetPrevRecording( int nSec, unsigned char* pBuf) // Get the last n sec of recording
{
	int nBytes = nSec * 16 /*bits/frame*/ * 16000 /*frames_per_sec*/ / 8;

	return m_pRingBuffer->ReadPrevBuffer( pBuf, nBytes);
}

// End data from recording buffer
int AlsaPcmRecorder::EndAudioCapture()
{
#ifdef _DEBUG
    std::cout << "Stopping Audio Capture\n";
#endif

    stop_capture();

    void *status;
    int rc = pthread_join(m_threadAudioCapture, &status);
    if (rc) {
        printf("ERROR; return code from pthread_join() is %d\n", rc);
        exit(-1);
    }

    printf("Stopped Audio Capture\n");

    return (true);
}
